type IEventDelegateHandlerProcess = (({target: any}, targetEl: HTMLElement) => void);

interface IEventDelegateHandler {
    name: string;
    handler: null | IEventDelegateHandlerProcess;
    destroy: () => void;
}

const delegateEventKeyName = 'data-delegate-event-key-' + Date.now().toString();

let registerCount = 0;

const eventHandlers = [];

function formatSelector(selector) {

    if ('string' === (typeof selector.class)) {
        const classes = selector.class.split(',').map(name => name.trim()).filter(name => !!name);
        selector.class = 0 < classes.length ? classes : null;
    } else {
        selector.class = null;
    }

    if ('string' === (typeof selector.id)) {
        const ids = selector.id.split(',').map(name => name.trim()).filter(name => !!name);
        selector.id = 0 < ids.length ? ids : null;
    } else {
        selector.id = null;
    }

    'function' !== (typeof selector.validator) && (selector.validator = null);

}

function isTargetElement(el, selector) {

    const classes = selector.class;
    const elClasses = ` ${el.className.trim()} `;
    if (!!classes && 0 < elClasses.length) {
        for (let index = 0, len = classes.length; index < len; index++) {
            const className = classes[index];
            if (-1 < elClasses.indexOf(` ${className} `)) {
                return true;
            }
        }
    }

    const ids = selector.id;
    const elId = el.id;
    if (!!ids && !!elId) {
        if (-1 < ids.indexOf(elId)) {
            return true;
        }
    }

    return !!selector.validator ? selector.validator(el) : false;

}

function findTargetElement(startEl, selector, stopEl) {

    if (!selector) {
        return startEl;
    }

    do {
        if (isTargetElement(startEl, selector)) {
            return startEl;
        }
        startEl = startEl.parentElement;
    } while (!!startEl && startEl !== stopEl)

    if (!!startEl && isTargetElement(startEl, selector)) {
        return startEl;
    }

    return null;

}

function formatHandler(handlers) {
    return Object.keys(handlers).map(function (eventName) {
        let sourceTrigger = handlers[eventName];
        const handler: IEventDelegateHandler = {
            name: eventName,
            handler: null,
            destroy() {
                // sourceTrigger = null;
            },
        };

        if ('function' === (typeof sourceTrigger)) {

            handler.handler = sourceTrigger;

        } else if (sourceTrigger instanceof Array) {

            sourceTrigger.forEach(function (trigger) {
                !!trigger.selector && formatSelector(trigger.selector);
            });
            handler.handler = function (event) {
                const stopEl = this;
                sourceTrigger.forEach(function (trigger) {
                    if ('function' === (typeof trigger)) {
                        trigger(event);
                    } else {
                        const targetEl = findTargetElement(event.target, trigger.selector, stopEl);
                        if (!trigger.selector || !!targetEl) {
                            trigger.handler(event, targetEl);
                        }
                    }
                });
            };

        } else {

            !!sourceTrigger.selector && formatSelector(sourceTrigger.selector);
            handler.handler = function (event) {
                const stopEl = this;
                const targetEl = findTargetElement(event.target, sourceTrigger.selector, stopEl);
                if (!sourceTrigger.selector || !!targetEl) {
                    sourceTrigger.handler(event, targetEl);
                }
            };

        }

        return handler;
    });
}


interface IEventDelegateRegisterParam {
    bind: (
        el: HTMLElement,
        binding: { value: {selector:string;}[] | IEventDelegateHandlerProcess },
    ) => void;
    unbind: (el: HTMLElement) => void;
}

type IEventDelegateRegister = (param: IEventDelegateRegisterParam) => void;

export function registerEventDelegate(register: IEventDelegateRegister) {
    const config = {
        bind(el, binding) {

            const events = binding.value;
            if (
                (events instanceof Array && 0 === events.length) ||
                ('object' === (typeof events) && 0 === Object.keys(events).length)
            ) {
                return;
            }

            // 缓存事件处理器，封装事件代理
            const handlers = formatHandler(events);
            handlers.forEach(function (trigger) {
                el.addEventListener(trigger.name, trigger.handler);
            });
            eventHandlers.push(handlers);

            // 记录该元素在缓存中的事件处理器的索引到元素上
            const triggerKey = registerCount++;
            el.setAttribute(delegateEventKeyName, triggerKey);

        },
        unbind(el) {

            let triggerKey = el.getAttribute(delegateEventKeyName);
            if (
                !!triggerKey &&
                !isNaN(triggerKey = +triggerKey) &&
                0 <= triggerKey &&
                triggerKey < registerCount
            ) {

                const handlers = eventHandlers[triggerKey];

                handlers.forEach(function (trigger) {
                    el.removeEventListener(trigger.name, trigger.handler);
                    trigger.destroy();
                });

                el.removeAttribute(delegateEventKeyName);
                eventHandlers[triggerKey] = null;

            }

        },
    };

}
